
package com.trydofor.a9task.base.code.help;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import com.trydofor.aloe.exception.Exceptions;

/**
 * @author : Shi Rongjiu (www.trydofor.com)
 * @date : Nov 7, 2012
 * @see :
 */

public class CodeTree {
    
    //===================================== global cache =================================
    private static final Map<String, CodeTree>        KEY_NODE   = new HashMap<String, CodeTree>();
    private static final Map<Integer, CodeTree>       ID_NODE    = new HashMap<Integer, CodeTree>();
    private static final Map<Integer, List<CodeTree>> ID_LIST    = new HashMap<Integer, List<CodeTree>>();
    
    private static final ReadWriteLock                LOCK_ROOT  = new ReentrantReadWriteLock();
    private static final Lock                         LOCK_READ  = LOCK_ROOT.readLock();
    private static final Lock                         LOCK_WRITE = LOCK_ROOT.writeLock();
    
    private static final List<CodeTree>               NODE_NULL  = Collections.unmodifiableList(new ArrayList<CodeTree>(
                                                                         0));
    private static final Comparator<CodeTree>         ORDERBY    = new Comparator<CodeTree>() {
                                                                     
                                                                     @Override
                                                                     public int compare(CodeTree o1, CodeTree o2) {
                                                                         int g = o1.code().main().getGroupId()
                                                                                 - o2.code().main().getGroupId();
                                                                         if (g == 0) {
                                                                             return o1.code().main().getOrderBy()
                                                                                    - o2.code().main().getOrderBy();
                                                                         }
                                                                         else {
                                                                             return g;
                                                                         }
                                                                     }
                                                                 };
    
    /**
     * put the code
     * 
     * @param node the node
     * @return the wrapped code (code-tree)
     */
    public static CodeTree put(Code code) {
        if (code == null) return null;
        LOCK_WRITE.lock();
        try {
            // check
            CodeTree oldKyNode = KEY_NODE.get(code.main().getCodeKey());
            CodeTree oldIdNode = ID_NODE.get(code.main().getId());
            Exceptions.checkIllegalArgument(oldKyNode != oldIdNode, "put code[%s], but exists 2 different code", code);
            
            // pool node/ replace or insert
            CodeTree newNode = new CodeTree(code);
            Integer newId = code.main().getId();
            List<CodeTree> newList = ID_LIST.get(newId);
            if (newList != null) { // no need to write lock,before put to poll
                newNode.node = Collections.unmodifiableList(newList); // read-only-view
            }
            KEY_NODE.put(code.main().getCodeKey(), newNode);
            ID_NODE.put(newId, newNode);
            
            // update group
            Integer oldGrpId = oldIdNode == null ? null : oldIdNode.code().main().getGroupId();
            Integer newGrpId = code.main().getGroupId();
            
            if (oldIdNode != null && oldGrpId != null && !oldGrpId.equals(newGrpId)) { // change group
                CodeTree oldGrpCode = ID_NODE.get(oldGrpId);
                List<CodeTree> oldGrpList = ID_LIST.get(oldGrpId);
                if (oldGrpCode != null) { // lock update
                    syncCutList(oldGrpCode, oldGrpList, oldIdNode);
                }
                else { // without lock
                    asyncCutList(oldGrpList, oldIdNode);
                }
            }
            
            CodeTree newGrpCode = ID_NODE.get(newGrpId);
            List<CodeTree> newGrpList = ID_LIST.get(newGrpId);
            if (newGrpList == null) {
                newGrpList = new ArrayList<CodeTree>();
                ID_LIST.put(newGrpId, newGrpList);
            }
            
            if (newGrpCode != null) { // lock update
                newGrpCode.wLock.lock();
                try {
                    asyncCutList(newGrpList, oldIdNode);
                    asyncAddSort(newGrpList,newNode);
                    if (newGrpCode.node == NODE_NULL) {
                        newGrpCode.node = Collections.unmodifiableList(newGrpList);
                    }
                }
                finally {
                    newGrpCode.wLock.unlock();
                }
            }
            else { // without lock
                asyncCutList(newGrpList, oldIdNode);
                asyncAddSort(newGrpList,newNode);
            }
            
            return oldKyNode;
        }
        finally {
            LOCK_WRITE.unlock();
        }
    }
    
    /**
     * cut the code by id
     * 
     * @param id code's id
     * @return the wrapped code (code-tree)
     * @throws IllegalArgumentException if cut bole
     */
    public static CodeTree cut(Integer id) {
        if (id == null) return null;
        LOCK_WRITE.lock();
        try {
            CodeTree old = ID_NODE.get(id);
            if (old != null) {
                Exceptions.checkIllegalArgument(!old.isLeaf(), "can not cut the bole");
                
                ID_NODE.remove(id);
                KEY_NODE.remove(old.code().main().getCodeKey());
                Integer gid = old.code().main().getGroupId();
                CodeTree sync = ID_NODE.get(gid);
                List<CodeTree> list = ID_LIST.get(gid);
                syncCutList(sync, list, old);
            }
            return old;
        }
        finally {
            LOCK_WRITE.unlock();
        }
    }
    
    /**
     * cut the code by key
     * 
     * @param key the code key
     * @return the wrapped code (code-tree)
     * @throws IllegalArgumentException if cut bole
     */
    public static CodeTree cut(String key) {
        if (key == null) return null;
        LOCK_WRITE.lock();
        try {
            CodeTree old = KEY_NODE.get(key);
            if (old != null) {
                Exceptions.checkIllegalArgument(!old.isLeaf(), "can not cut the bole");
                
                KEY_NODE.remove(key);
                ID_NODE.remove(old.code().main().getId());
                Integer gid = old.code().main().getGroupId();
                CodeTree sync = ID_NODE.get(gid);
                List<CodeTree> list = ID_LIST.get(gid);
                syncCutList(sync, list, old);
            }
            return old;
        }
        finally {
            LOCK_WRITE.unlock();
        }
    }
    
    /**
     * get the code by id
     * 
     * @param id code's id
     * @return the wrapped code (code-tree)
     */
    public static CodeTree get(Integer id) {
        if (id == null) return null;
        LOCK_READ.lock();
        try {
            return ID_NODE.get(id);
        }
        finally {
            LOCK_READ.unlock();
        }
    }
    
    /**
     * get all code (read-only, not ordered)
     * 
     * @param id code's id
     * @return the wrapped code (code-tree)
     */
    public static Collection<CodeTree> all() {
        LOCK_READ.lock();
        try {
            return Collections.unmodifiableCollection(ID_NODE.values());
        }
        finally {
            LOCK_READ.unlock();
        }
    }
    
    /**
     * get the count of all code
     * 
     * @return the count
     */
    public static int size() {
        LOCK_READ.lock();
        try {
            return ID_NODE.size();
        }
        finally {
            LOCK_READ.unlock();
        }
    }
    
    /**
     * get the count of all code
     * 
     * @return the count
     */
    public static void reset() {
        LOCK_WRITE.lock();
        try {
            KEY_NODE.clear();
            ID_NODE.clear();
            ID_LIST.clear();
        }
        finally {
            LOCK_WRITE.unlock();
        }
    }
    
    /**
     * get the code by key
     * 
     * @param key the code key
     * @return the wrapped code (code-tree)
     */
    public static CodeTree get(String key) {
        if (key == null) return null;
        LOCK_READ.lock();
        try {
            return KEY_NODE.get(key);
        }
        finally {
            LOCK_READ.unlock();
        }
    }
    
    ///////////////
    private static void asyncAddSort(List<CodeTree> list, CodeTree node){
        if(list == null || node == null) return;
        if(node.code().main().getId() == node.code().main().getGroupId()) return;
        list.add(node);
        Collections.sort(list, ORDERBY);
    }
    private static void asyncCutList(List<CodeTree> list, CodeTree node) {
        if (list == null || node == null) return;
        for (Iterator<CodeTree> it = list.iterator(); it.hasNext();) {
            CodeTree ct = it.next();
            if (ct.code().main().getId() == node.code().main().getId()) {
                it.remove();
            }
        }
    }
    
    private static void syncCutList(CodeTree sync, List<CodeTree> list, CodeTree node) {
        if (sync == null) return;
        sync.wLock.lock();
        try {
            asyncCutList(list, node);
            if (list == null || list.isEmpty()) {
                sync.node = NODE_NULL;
            }
            else {
                if (sync.node == NODE_NULL) {
                    sync.node = Collections.unmodifiableList(list);
                }
            }
        }
        finally {
            sync.wLock.unlock();
        }
    }
    
    //=====================================  local cache =====================================
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock          rLock  = rwLock.readLock();
    private final Lock          wLock  = rwLock.writeLock();
    
    private final Code          code;
    private List<CodeTree>      node   = NODE_NULL;
    
    private CodeTree(Code code) {
        this.code = code;
    }
    
    /**
     * get the code
     * @return
     */
    public Code code() {
        return code;
    }
    
    /**
     * true if has no child node
     * @return
     */
    public boolean isLeaf() {
        rLock.lock();
        try {
            return node.size() == 0;
        }
        finally {
            rLock.unlock();
        }
    }
    
    /**
     * get read-only ordered child list (leaf and bole)
     * 
     * @return
     */
    public List<CodeTree> list() {
        rLock.lock();
        try {
            return node;
        }
        finally {
            rLock.unlock();
        }
    }
    
    /**
     * get a ordered copy of leaf nodes
     * 
     * @return
     */
    public List<CodeTree> leaf() {
        rLock.lock();
        try {
            List<CodeTree> ls = new ArrayList<CodeTree>(node.size());
            for (CodeTree ct : node) {
                if (ct.isLeaf()) {
                    ls.add(ct);
                }
            }
            return ls;
        }
        finally {
            rLock.unlock();
        }
    }
    
    /**
     * get a ordered copy of bole nodes
     * 
     * @return
     */
    public List<CodeTree> bole() {
        rLock.lock();
        try {
            List<CodeTree> ls = new ArrayList<CodeTree>(node.size());
            for (CodeTree ct : node) {
                if (!ct.isLeaf()) {
                    ls.add(ct);
                }
            }
            return ls;
        }
        finally {
            rLock.unlock();
        }
    }
}
